/**
 * 
 * Copyright (C) 2011 Cody Stoutenburg . All rights reserved.
 *
 *       This program is free software; you can redistribute it and/or
 *       modify it under the terms of the GNU Lesser General Public License
 *       as published by the Free Software Foundation; either version 2.1
 *       of the License, or (at your option) any later version.
 *
 *       This program is distributed in the hope that it will be useful,
 *       but WITHOUT ANY WARRANTY; without even the implied warranty of
 *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *       GNU Lesser General Public License for more details.
 *
 *       You should have received a copy of the GNU Lesser General Public License
 *       along with this program; if not, write to the Free Software
 *       Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 * 
 */
package ca.usherbrooke.model.world;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Shape;

import ca.usherbrooke.model.bridge.IEntityModelObject;
import ca.usherbrooke.model.bridge.IEquipmentModelObject;
import ca.usherbrooke.model.bridge.IProtectionModelObject;
import ca.usherbrooke.model.bridge.IWeaponModelObject;
import ca.usherbrooke.model.entity.EntityDTO;
import ca.usherbrooke.model.entity.EntityModel;
import ca.usherbrooke.model.entity.IEntityGame;
import ca.usherbrooke.model.entity.IEntityModel;
import ca.usherbrooke.model.equipment.EquipmentModel;
import ca.usherbrooke.model.equipment.ProtectionModel;
import ca.usherbrooke.model.equipment.WeaponModel;
import ca.usherbrooke.model.feature.GroundModel;
import ca.usherbrooke.model.feature.WallModel;
import ca.usherbrooke.tools.Tool;
import ca.usherbrooke.tools.position.Vector2D;

/**
 * @author Cody Stoutenburg
 * 
 */
public class WorldModel extends BasicWorldModel implements IWorldModel {
	private static final long serialVersionUID = 1L;

	private static boolean SHOW_PERFORMANCE = false;

	private EquipmentModel[] allEquipments;

	// all entity on map
	private IEntityGame[] allEntity;

	// this list is just a temporary to know who collid with wall at each update
	private transient final List<IEntityGame> collidWithWall;

	public WorldModel(Integer blockSize, Integer x, Integer y, Integer width,
			Integer height) {
		super(blockSize, x, y, width, height);

		allEquipments = new EquipmentModel[0];
		allEntity = new IEntityGame[0];

		collidWithWall = new ArrayList<IEntityGame>();

		realTimeElapse = 0L;
	}

	public void addEntity(IEntityGame entity) {
		setGroundForEntity(entity);
		allEntity = Arrays.copyOf(allEntity, allEntity.length + 1);
		allEntity[allEntity.length - 1] = entity;
	}

	public void removeEntity(IEntityGame entity) {
		Boolean contain = false;
		for (int i = 0; i < allEntity.length; ++i) {
			if (allEntity[i].equals(entity)) {
				contain = true;
			}
		}

		if (contain) {
			IEntityGame[] copy = Arrays.copyOf(allEntity, allEntity.length);

			allEntity = new IEntityGame[allEntity.length - 1];
			Tool.removeFromTab(copy, entity, allEntity);
		}
	}

	@Override
	public void update(int delta) {
		realTimeElapse += delta;
		final long startTime = System.nanoTime();
		final long endTime;

		collidWithWall.clear();

		for (int i = 0; i < allEntity.length; ++i) {
			IEntityGame entity = allEntity[i];

			if (entity.isDead()) {
				this.removeEntity(entity);
			} else {
				entity.doLogic(delta);
				entity.doMove(delta);

				List<WallModel> walls = getWall(entity.getSize());
				if (!walls.isEmpty()) {
					Boolean undo = true;
					if (entity.isJumping()) {
						// jump over the wall ?
						undo = false;
						for (WallModel wallModel : walls) {
							if (!wallModel.canJumOver()) {
								undo = true;
							}
						}
					}
					if (undo) {
						entity.doMove(-delta);
						collidWithWall.add(entity);
					}
				}
			}
		}

		boolean haveCollision = true;
		while (haveCollision) {
			haveCollision = false;
			for (int i = 0; i < allEntity.length; ++i) {
				IEntityGame first = allEntity[i];
				for (int j = i + 1; j < allEntity.length; ++j) {
					IEntityGame second = allEntity[j];
					Shape firstPosition = first.getSize();
					Shape secondPosition = second.getSize();
					if (firstPosition.intersects(secondPosition)
							|| firstPosition.contains(secondPosition)
							|| secondPosition.contains(firstPosition)) {
						if (collidWithWall.contains(first)) {
							second.doMove(-delta);
							collidWithWall.add(second);
						} else if (collidWithWall.contains(second)) {
							first.doMove(-delta);
							collidWithWall.add(first);
						} else {
							first.doMove(-delta);
							second.doMove(-delta);
						}
					}
				}
			}
		}

		for (IEntityGame entity : allEntity) {
			setGroundForEntity(entity);
		}

		endTime = System.nanoTime();
		final long duration = endTime - startTime;
		// si duration > 1 ms print it
		if (SHOW_PERFORMANCE && duration > 1000000) {
			System.out.println("duration: "
					+ TimeUnit.MILLISECONDS.convert(duration,
							TimeUnit.NANOSECONDS) + "ms " + duration);
		}
	}

	@Override
	protected void setGroundForEntity(IEntityGame entity) {
		GroundModel ground = this.getGround(entity.getCenterPosition());
		entity.setWalkOn(ground);
	}

	public void addEquipment(EquipmentModel equipment) {
		allEquipments = Arrays.copyOf(allEquipments, allEquipments.length + 1);
		allEquipments[allEquipments.length - 1] = equipment;
	}

	public void removeEquipment(EquipmentModel equipment) {
		Boolean contain = false;
		for (int i = 0; i < allEquipments.length; ++i) {
			if (allEquipments[i].equals(equipment)) {
				contain = true;
			}
		}

		if (contain) {
			EquipmentModel[] copy = Arrays.copyOf(allEquipments,
					allEquipments.length);

			allEquipments = new EquipmentModel[allEquipments.length - 1];
			Tool.removeFromTab(copy, equipment, allEquipments);
		}
	}

	@Override
	public List<EquipmentModel> getEquipment(Vector2D position) {
		List<EquipmentModel> result = new ArrayList<EquipmentModel>();
		for (EquipmentModel equipments : allEquipments) {
			if (equipments.getSize().contains(position.getX().floatValue(),
					position.getY().floatValue())) {
				result.add(equipments);
			}
		}
		return result;
	}

	@Override
	public List<EquipmentModel> getEquipment(Rectangle position) {
		List<EquipmentModel> result = new ArrayList<EquipmentModel>();
		for (EquipmentModel equipments : allEquipments) {
			if (equipments.getSize().contains(position)
					|| equipments.getSize().intersects(position)) {
				result.add(equipments);
			}
		}
		return result;
	}

	@Override
	public EquipmentModel getEquipment(Long guid) {
		EquipmentModel result = null;
		for (EquipmentModel equipments : allEquipments) {
			if (equipments != null && equipments.getGuid().equals(guid)) {
				result = equipments;
				break;
			}
		}
		return result;
	}

	@Override
	public List<EquipmentModel> getAllEquipments() {
		return Arrays.asList(allEquipments);
	}

	@Override
	public IEntityModel getEntity(Long guid) {
		IEntityModel model = null;
		for (IEntityModel current : this.allEntity) {
			if (current.getGuid().equals(guid)) {
				model = current;
				break;
			}
		}
		return model;
	}

	@Override
	public IEntityModel getEntity(Vector2D position) {
		IEntityModel result = null;
		for (IEntityModel entity : allEntity) {
			if (entity.getSize().contains(position.getX().floatValue(),
					position.getY().floatValue())) {
				result = entity;
				break;
			}
		}
		return result;
	}

	@Override
	public List<IEntityModel> getEntities(Rectangle position) {
		List<IEntityModel> result = new ArrayList<IEntityModel>();
		for (IEntityModel entity : allEntity) {
			if (entity.getSize().contains(position)
					|| entity.getSize().intersects(position)) {
				result.add(entity);
			}
		}
		return result;
	}

	@Override
	public IEntityModel createEntity(IEntityModelObject entity) {
		EntityModel model = new EntityModel(entity);
		this.addEntity(model);
		return model;
	}

	@Override
	public EquipmentModel createEquipment(IEquipmentModelObject equipement) {
		EquipmentModel model = null;
		if (equipement instanceof IProtectionModelObject) {
			model = new ProtectionModel((IProtectionModelObject) equipement);
		} else if (equipement instanceof IWeaponModelObject) {
			model = new WeaponModel((IWeaponModelObject) equipement);
		} else {
			model = new EquipmentModel(equipement);
		}
		this.addEquipment(model);
		return model;
	}

	@Override
	public List<IEntityModel> getAllEntities() {
		List<IEntityModel> result = new ArrayList<IEntityModel>();
		for (IEntityModel entity : allEntity) {
			result.add(entity);
		}
		return result;
	}

	public IWorldModel getSubWorld(Rectangle rectangle) {
		// because entity could be at a border of the world
		Float realWidth = rectangle.getWidth();
		if (rectangle.getMinX() < 0) {
			realWidth += rectangle.getMinX();
		}
		if (rectangle.getMaxX() > this.getWidth()) {
			realWidth -= (rectangle.getMaxX() - this.getWidth());
		}

		Float realHeight = rectangle.getHeight();
		if (rectangle.getMinY() < 0) {
			realHeight += rectangle.getMinY();
		}
		if (rectangle.getMaxY() > this.getHeight()) {
			realHeight -= (rectangle.getMaxY() - this.getHeight());
		}

		Float x = Math.max(0, rectangle.getMinX());
		Float y = Math.max(0, rectangle.getMinY());
		WorldModel world = new WorldModel(blockSize, x.intValue(),
				y.intValue(), realWidth.intValue(), realHeight.intValue());

		world.realTimeElapse = realTimeElapse;

		Pair<Integer, Integer> idxMin = positionToIdx(x, y);
		Pair<Integer, Integer> idxMax = positionToIdx((x + realWidth),
				(y + realHeight));

		for (int i = idxMin.first; i < idxMax.first; ++i) {
			for (int j = idxMin.second; j < idxMax.second; j++) {
				world.allGrounds[i][j] = allGrounds[i][j];
			}
		}

		List<IEntityModel> entities = this.getEntities(rectangle);
		for (IEntityModel entity : entities) {
			world.addEntity(new EntityDTO(entity));
		}

		List<EquipmentModel> equipments = this.getEquipment(rectangle);
		for (EquipmentModel equipment : equipments) {
			world.addEquipment(equipment);
		}

		return world;
	}

	@Override
	public IWorldModel copyToTransfert() {
		return this.getSubWorld(new Rectangle(0f, 0f, this.getWidth(), this
				.getHeight()));
	}

	@Override
	public IWorldModel getVision(Long guid) {
		IWorldModel result = null;
		EntityModel entity = (EntityModel) this.getEntity(guid);
		if (entity != null) {
			result = this.getSubWorld(entity.getViewingArea());
		}
		return result;
	}
}
